using System;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextTemplating.VSHost;
using Microsoft.Win32;
using Microsoft.Modeling.StoryboardDesigner.DslPackage.Extensions.Common;
using Microsoft.VisualStudio.TextTemplating;

namespace Microsoft.Modeling.StoryboardDesigner.DslPackage
{
    internal partial class StoryboardDesignerCommandSet
    {
        #region Constants
        private const string ModelFileNameMarker = "%MODELFILENAME%";
        private const string ActionMarker = "%ACTION%";
        private const string ResponseMarker = "%RESPONSE%";
        private const string ScenarioNameMarker = "%SCENARIONAME%";
        private const string ScenarioHtmlPageTemplate = @"ScenarioPageTemplate.tt";
        private const string StoryboardIndexHtmlPageTemplate = "StoryboardIndexTemplate.tt";
        private const string StubName = "StoryboardDesigner_cfe3b3fb-bb35-40aa-8056-79491cffceb6";
        private const string StubRelativeHive = @"Software\Microsoft\AppEnv\9.0\Apps";
        #endregion

        #region Context menu handling

        /// <summary>
        /// Returns a flag indicating whether the "Publish Storyboard"
        /// context menu should be displayed.
        /// </summary>
        /// <remarks>Enable the command when at least one Scenario exists
        /// in the diagram, and the Publish property has been set.</remarks>
        private void OnStatusPublishStoryboard(object sender, EventArgs args)
        {
            MenuCommand command = sender as MenuCommand;

            if(command == null)
            {
                return;
            }

            // Enabled if there is at least one scenario in the 
            // Store (NB we are checking the whole store, not just
            // the scenarios associated with this diagram. However,
            // ok in this case).
            command.Enabled =
                (this.CurrentDocData.Store.ElementDirectory.FindElements<Scenario>().Count > 0);
        }

        /// <summary>
        /// Context menu handler - called when "Publish Storyboard"
        /// is clicked.
        /// </summary>
        private void OnPublishStoryboard(object sender, EventArgs args)
        {
            DialogResult result;

            try
            {
                //Assume we have a valid selection
                StoryboardDiagram storyboardDiagram =
                    this.CurrentDocView.CurrentDiagram as StoryboardDiagram;

                if(storyboardDiagram == null)
                {
                    return;
                }

                Storyboard storyboard = storyboardDiagram.ModelElement as Storyboard;

                if(storyboard == null)
                {
                    return;
                }

                // Assume everything is ok
                bool valid = true;

                // Check that the specified location is valid
                if(string.IsNullOrEmpty(storyboard.PublishLocation))
                {
                    valid = false;
                    result = PackageUtility.ShowMessageBox(this.ServiceProvider,
                        string.Format(
                            CultureInfo.CurrentCulture,
                            Properties.Resources.SpecifyPublishLocation,
                            Environment.NewLine),
                        OLEMSGBUTTON.OLEMSGBUTTON_YESNO,
                        OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
                        OLEMSGICON.OLEMSGICON_WARNING);

                    if(result == DialogResult.No)
                    {
                        return;
                    }
                }
                else if(!Directory.Exists(storyboard.PublishLocation))
                {
                    valid = false;
                    result = PackageUtility.ShowMessageBox(this.ServiceProvider,
                        string.Format(
                            CultureInfo.CurrentCulture,
                            Properties.Resources.InvalidPublishLocation,
                            Environment.NewLine),
                        OLEMSGBUTTON.OLEMSGBUTTON_YESNO,
                        OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
                        OLEMSGICON.OLEMSGICON_WARNING);

                    if(result == DialogResult.No)
                    {
                        return;
                    }
                }

                // Give the user a chance to choose a valid publish location
                if(!valid)
                {
                    if(!ChoosePublishLocation(storyboard))
                    {
                        return;
                    }
                }

                // Build the pages for the storyboard
                PublishStoryboard(storyboardDiagram, this.CurrentDocData.FileName);

                // Tell the user we are done
                result = PackageUtility.ShowMessageBox(this.ServiceProvider,
                    string.Format(
                        CultureInfo.CurrentCulture,
                        Properties.Resources.StoryboardPublished,
                        Environment.NewLine),
                    OLEMSGBUTTON.OLEMSGBUTTON_YESNO,
                    OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
                    OLEMSGICON.OLEMSGICON_INFO);

                // Open the folder if appropriate
                if(result == DialogResult.Yes)
                {
                    Process.Start("explorer", storyboard.PublishLocation);
                }

            }
            catch(Exception ex)
            {
                PackageUtility.ShowError(
                    this.ServiceProvider,
                    string.Format(
                        CultureInfo.CurrentCulture,
                        Properties.Resources.GeneralException,
                        Environment.NewLine,
                        ex.Message));
            }
        }

        private bool ChoosePublishLocation(Storyboard storyboard)
        {
            FolderBrowserDialog dialog = new FolderBrowserDialog();
            dialog.Description = Properties.Resources.ChooseFolderDialogTitle;
            dialog.ShowNewFolderButton = true;
            DialogResult result = dialog.ShowDialog();

            if(result == DialogResult.Cancel)
            {
                return false;
            }

            // Store the folder specified by the user
            using(Transaction t = storyboard.Store.TransactionManager.BeginTransaction("Set publish location"))
            {
                storyboard.PublishLocation = dialog.SelectedPath;
                t.Commit();
            }

            return true;
        }

        #endregion

        #region Publish storyboard code
        /// <summary>
        /// Publishes the scenarios in the supplied storyboard
        /// </summary>
        /// <param name="model"></param>
        private void PublishStoryboard(StoryboardDiagram diagram, string modelFileName)
        {
            // Sanity check
            Debug.Assert(diagram != null, "Error - diag is null");

            Storyboard model = diagram.ModelElement as Storyboard;
            Debug.Assert(model != null, "Error - model is null");

            // Get a ref to the T4 processor
            ITextTemplating textTemplating =
                this.ServiceProvider.GetService<ITextTemplating, STextTemplating>();

            if(textTemplating == null)
            {
                PackageUtility.ShowError(
                    this.ServiceProvider,
                    string.Format(
                        CultureInfo.CurrentCulture,
                        Properties.Resources.NullTemplatingEngine));

                return;
            }

            // Walk through the list of scenarios
            foreach(StoryboardElement element in model.Elements)
            {
                Scenario scenario = element as Scenario;

                if(scenario != null)
                {
                    // Build pages for this scenario
                    BuildPageForScenario(scenario, model.PublishLocation, modelFileName, textTemplating);
                }
            }

            // Now build the index page
            string fileName = Path.GetFileNameWithoutExtension(modelFileName) + "_Index.htm";
            fileName = Path.Combine(model.PublishLocation, fileName);
            BuildIndexPage(StoryboardIndexHtmlPageTemplate, modelFileName, false, false, fileName, textTemplating);

            // Save the diagram image - same name as the model file, but
            // in the publish folder and with ".jpg"
            fileName = Path.GetFileName(modelFileName);
            fileName = Path.Combine(model.PublishLocation, fileName);
            fileName = fileName + ".jpg";

            try
            {
                DeleteExistingFile(fileName);
                SaveDiagramImage(diagram, fileName, Diagram.CreateBitmapPreference.FavorClarityOverSmallSize);
            }
            catch(Exception ex)
            {
                Debug.WriteLine("Exception occurred saving image file: " + ex.ToString());
            }
        }

        /// <summary>
        /// Builds and writes to the disc the page for the supplied scenario.
        /// </summary>
        private void BuildPageForScenario(
            Scenario scenario,
            string publishLocation,
            string modelFileName,
            ITextTemplating textTemplating)
        {
            string fileName;

            // Decide which pages to build
            fileName = Path.Combine(publishLocation, scenario.Name + ".htm");

            BuildScenarioPage(ScenarioHtmlPageTemplate, modelFileName, scenario.Name, false, false, fileName, textTemplating);

            // Now copy any images used by the scenario to the publish location
            CopyScenarioImage(scenario.Snapshot, publishLocation);
        }

        /// <summary>
        /// Copy any images used by the scenario to the publish
        /// location. Existing files are not over-written.
        /// </summary>
        private static void CopyScenarioImage(string fileName, string publishLocation)
        {
            // Give up if the source file doesn't exist
            if(string.IsNullOrEmpty(fileName) || !File.Exists(fileName))
            {
                return;
            }

            // Work out the new file name
            string newFileName = Path.Combine(
                    publishLocation,
                    Path.GetFileName(fileName));

            // Do nothing if the names are the same
            if(string.Compare(fileName, newFileName) == 0)
            {
                return;
            }

            try
            {
                DeleteExistingFile(newFileName);

                File.Copy(fileName, newFileName);
            }
            catch(Exception ex)
            {
                // Squash any exceptions when copying
                Debug.WriteLine("Exception occurred when copying: " + ex.ToString());
            }
        }

        private static void DeleteExistingFile(string fileName)
        {
            if(File.Exists(fileName))
            {
                // Ensure the file is not read-only
                File.SetAttributes(fileName, FileAttributes.Normal);

                // Delete the existing file
                File.Delete(fileName);
            }
        }

        private void BuildIndexPage(
            string ttFile,
            string modelFileName,
            bool action,
            bool response,
            string outputFileName,
            ITextTemplating textTemplating)
        {
            BuildScenarioPage(
                ttFile,
                modelFileName,
                string.Empty,
                action,
                response,
                outputFileName,
                textTemplating);
        }

        /// <summary>
        /// Builds an individual page and saves it to disc
        /// </summary>
        /// <param name="resourceName"></param>
        /// <param name="modelFileName"></param>
        /// <param name="scenarioName"></param>
        /// <param name="Action"></param>
        /// <param name="Response"></param>
        /// <param name="outputFileName"></param>
        /// <param name="textTemplating"></param>
        private void BuildScenarioPage(
            string ttFile,
            string modelFileName,
            string scenarioName,
            bool action,
            bool response,
            string outputFileName,
            ITextTemplating textTemplating)
        {
            string templateFile = Path.Combine(this.TemplatesPath, ttFile);

            if(File.Exists(templateFile))
            {
                string template = File.ReadAllText(templateFile);

                // Sanity checks - ensure the markers are in the template
                Debug.Assert(template.Contains(ModelFileNameMarker), string.Format("Error - cannot find \"{0}\" marker in template", ModelFileNameMarker));

                // Substitute in the values
                template = template.Replace(ModelFileNameMarker, modelFileName);
                template = template.Replace(ActionMarker, action.ToString().ToLower());
                template = template.Replace(ResponseMarker, response.ToString().ToLower());
                template = template.Replace(ScenarioNameMarker, scenarioName);

                // Execute the template
                string output = textTemplating.ProcessTemplate(templateFile, template, null, null);

                // Save the output to disc
                DeleteExistingFile(outputFileName);

                File.WriteAllText(outputFileName, output);
            }
        }

        string templatesPath;

        private string TemplatesPath
        {
            get
            {
                if(String.IsNullOrEmpty(templatesPath))
                {
                    string name = string.Format(CultureInfo.InvariantCulture, @"{0}\{1}", StubRelativeHive, StubName);

                    using(RegistryKey key = Registry.LocalMachine.OpenSubKey(name, false))
                    {
                        if(key != null)
                        {
                            templatesPath = key.GetValue("TemplatesPath") as string;
                        }
                    }
                }

                return templatesPath;
            }

            set
            {
                templatesPath = value;
            }
        }
        #endregion
    }
}